/*
 * routines to read tiff-files (.tif) and MetaMorph stack files (.stk)
 *
 * Time-stamp: <18 Jul 2006 11:58 BST tifflib.c km>
 *
 * Copyright (c) Solexa 2003, 2004, 2005, 2006
 * Author: Klaus Maisinger
 * This source file is covered by the "Solexa Public Source License"
 * agreement and bound by the terms therein.
 */
/*
 * References for the file formats can be found in the TIFF 6.0 Specification
 * (available from http://www.libtiff.org) and "MetaMorph Stack (STK) Image
 * File Format" (available from 
 * http://support.universal-imaging.com/docs/T10243.pdf). The stack format
 * is an extension to the TIFF format that allows multiple image planes to 
 * be stored.
 *
 * The following implementation provides some functions to read information 
 * from stack files. It requires the system header file tiff.h which is
 * provided by the libtiff implementation (www.libtiff.org).
 * Originally, this implementation assumed that this sotware runs on
 * a little-endian architecture. It has not been tested on big-endian platforms.
 */

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include "tiff.h"
#include "tifflib.h"

/* each 12-byte TIFF image file directory (IFD) entry has the following format:
   bytes 0-1 the tag that identifies the field
   bytes 2-3 type (1-byte, 2-ASCII, 3-short, 4-long, 5-rational) 
   bytes 4-7 count (number of values)
   bytes 8-11 value (or address if values don't fit in 4 bytes)
*/

#define MIN(x,y) ((x) < (y) ? (x) : (y))

/*
 * wrap common file access operations so that they can operate either
 * on the file on disk or on a copy in the main memory.
 * Returns 0 on success, -1 on failure.
 */
static int tiff_seek(TIFF_FILE *stream, long offset, int whence)
{
    if (stream->cacheflag) {
        /* file content cached in memory */
        if ((stream->data_pointer < 0) ||
            (stream->data_pointer >= stream->data_length))
            return -1;   
        switch (whence) {
        case SEEK_SET:
            stream->data_pointer = offset; break;
        case SEEK_CUR:
            stream->data_pointer += offset; break;
        case SEEK_END:
            stream->data_pointer = stream->data_length - offset; break;
        }
        if ((stream->data_pointer < 0) ||
            (stream->data_pointer >= stream->data_length))
            return -1;   
        return 0;
    }
    /* file content not cached: perform ordinary fseek */
    if (stream && (stream->file_stream))
        return fseek(stream->file_stream, offset, whence);
    else {
        fprintf(stderr, "Error: %s::tiff_seek():%i Invalid tiff file pointer!\n",
                        __FILE__, __LINE__);
    }
    return -1;
}

/*
 * Returns the number of items read
 */
static size_t tiff_read(void *ptr, size_t size, size_t nmemb, TIFF_FILE *stream)
{
    if (stream->cacheflag) {
        /* file is cached: copy memory to ptr */
        if ((stream->data_pointer < 0) ||
             (stream->data_pointer > stream->data_length - (long)(size*nmemb)))
            return 0;
        memcpy(ptr, (void *)(&(stream->data[stream->data_pointer])),
               size*nmemb);
        stream->data_pointer += size*nmemb;
        return (size_t)(size*nmemb);
    }
    /* file content not cached: perform ordinary fread */
    if (stream && (stream->file_stream))
        return fread(ptr, size, nmemb, stream->file_stream);
    else {
        fprintf(stderr, "Error: %s:tiff_read():%i Invalid tiff file pointer!\n",
                        __FILE__, __LINE__);
    }
    return 0;
}

/*
 * Routine is only used by MetaMorph functions.
 * TODO: Graceful error handling.
 */
static int tiff_getc(TIFF_FILE *stream)
{
    if (stream->cacheflag) {
        int result;
        assert(stream->data_pointer > 0);
        assert(stream->data_pointer < stream->data_length);
        result = (int)(stream->data[stream->data_pointer]);
        stream->data_pointer += 1;
        return result;
    }
    /* file content not cached: perform ordinary fgetc */
    if (stream && (stream->file_stream))
        return fgetc(stream->file_stream);
    else {
        fprintf(stderr, "Error: %s::tiff_read():%i Invalid tiff file pointer!\n",
                        __FILE__, __LINE__);
        exit(0);
    }
    return EOF;
}
    

static TIFF_FILE *new_tiff_file(void)
{
    struct tiff_file *new_file =
        (struct tiff_file *)malloc(sizeof(struct tiff_file));
    new_file->file_stream = NULL;
    new_file->ifd_table = NULL;
    new_file->next_file = NULL;
    new_file->little_endian = 1;
    new_file->ifd_len = 0;
    new_file->data = NULL;
    new_file->data_pointer = 0;
    new_file->data_length = 0;
    new_file->cacheflag = 0;
    return new_file;
}

static void delete_tiff_file(TIFF_FILE *obj)
{
    if (obj->ifd_table != NULL)
        free(obj->ifd_table);
    if (obj->cacheflag)
        free(obj->data);
    free(obj);
}

static int fread_tiff_header(TIFF_FILE * tf);

/*
 * open a Tiff file. 
 * There are two modes: 
 * <memory> is true: The whole file is read and cached in memory.
 * <memory> is false: Only the header information is cached.
 * Access to values of header fields or data still goes through 
 * file seeks.
 * Returns a pointer to the TIFF_FILE struct if successful,
 * 0 on failure.
 */
TIFF_FILE *fopen_tiff(const char *path, const char *mode, int memory)
{
    TIFF_FILE *tiff = new_tiff_file();
    FILE      *fp;
    char      error_msg[120];
    snprintf(error_msg, 120, "Error: %s::fopen_tiff(%s):%i",
                             __FILE__, path, __LINE__);
#if 0
    printf("Entering tifflib::fopen_tiff(%s, %s, %i)\n", path, mode, memory);
#endif
    {
        struct stat stbuf;
        if (stat(path, &stbuf)) {
            perror(error_msg); goto terminate;
        }
        tiff->data_length = (long)stbuf.st_size;
    }
    if (memory) {
        long   count;

        tiff->data = (uint8 *)malloc(tiff->data_length * sizeof(uint8));
        tiff->cacheflag = 1;
        fp = fopen(path, mode);
        if (!fp) {
            perror(error_msg); goto terminate;
        }
        count = fread(tiff->data, sizeof(uint8), (size_t)(tiff->data_length), fp);
        if (count != tiff->data_length) {
            fprintf(stderr, "Error: Not enough bytes read from file: "
                    "Expected %li, read %li\n", tiff->data_length, count);
            goto terminate;
        }
        if (fclose(fp)) {
            perror(error_msg); goto terminate;
        }        
#if 0
        printf("Loaded %li bytes from %s.\n", tiff->data_length, path);
#endif
    }
    else {
        fp = fopen(path, mode);
        if (!fp) {
            perror(error_msg); goto terminate;
        }
        tiff->file_stream = fp;
    }
    if (fread_tiff_header(tiff)) {
        perror(error_msg); goto terminate;
    }
    return tiff;
terminate:
    delete_tiff_file(tiff);
    return 0;  
}

int fclose_tiff(TIFF_FILE * stream)
{
    int result = 0;
    if (! stream->cacheflag)
        result = fclose(stream->file_stream);
    delete_tiff_file(stream);
    return result;
}

/*
 * returns 1 if the tiff file corresponding to the file pointer <tf>
 * is little-endian and 0 otherwise
 */
static int get_tiff_little_endianness(TIFF_FILE *tf)
{
    return tf->little_endian;
}

/*-----------------------------------------------------------------------*/
/* conversion routines (little-endian/big-endian)                        */
/*-----------------------------------------------------------------------*/
static uint16 getuint16(uint8 * buffer, int little_endian)
{
    if (little_endian)
        return (buffer[1] << 8) + buffer[0];
    else
        return (buffer[0] << 8) + buffer[1];
}

/*
 * read a single 16-bit unsigned short from the file <tf>
 */
static int fgetuint16(TIFF_FILE *tf, int little_endian, uint16 *result)
{
    uint8 buffer[2];

    *result = 0;
    if (tiff_read(buffer, sizeof(uint8), 2, tf) != 2*sizeof(uint8))
        return 1;
    *result = getuint16(buffer, little_endian);
    return 0;
    
}

static uint32 getuint32(uint8 * buffer, int little_endian)
{
    uint32 result;
    if (little_endian)
        result =
            (buffer[3] << 24) + (buffer[2] << 16) + (buffer[1] << 8) +
            buffer[0];
    else
        result =
            (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) +
            buffer[3];
    return result;
}

/*
 * read a single 32-bit unsigned long from the file <tf>
 */
static int fgetuint32(TIFF_FILE *tf, int little_endian, uint32 *result)
{
    uint8 buffer[4];

    *result = 0;
    if (tiff_read(buffer, sizeof(uint8), 4, tf) != 4 * sizeof(uint8))
        return 1;
    *result = getuint32(buffer, little_endian);
    return 0;
}

/*-----------------------------------------------------------------------*/
/*-----------------------------------------------------------------------*/

/*
 * read TIFF header information from a tiff file
 */
static int fread_tiff_header(TIFF_FILE * tf)
{
    uint8 header[8];
    uint16 order;
    uint32 ifd_address;
    uint8 *buffer, *current;
    int i;

    if ((tf == NULL) || ((tf->file_stream == NULL) && (!tf->cacheflag)))
        return 3;               /* TIFF file has not added to linked list */
    if ((tf->ifd_len != 0) && (tf->ifd_table != NULL))
        return 0;               /* header has already been read */

    if (tiff_seek(tf, 0, SEEK_SET) != 0)
        return 6;
    if (tiff_read(header, sizeof(uint8), 8, tf) != (size_t)(8*sizeof(uint8)))
        return 6;

    /* first two bytes of header show if file is little-endian (0x4949) 
       or big-endian (0x4d4d).  */
    order = getuint16(header, 1);
    if (order == TIFF_LITTLEENDIAN)
        tf->little_endian = 1;
    else if (order == TIFF_BIGENDIAN)
        tf->little_endian = 0;
    else
        return 2;

    /* verify that file is tiff file (bytes 2-3 are 0x2a) */
    if (getuint16(header + 2, tf->little_endian) != 0x2a)
        return 1;

    /* read IFD addess */
    ifd_address = getuint32(header + 4, tf->little_endian);
    if ((ifd_address < 8) ||
        ((long)ifd_address > tf->data_length - 2))
        return 4;
    if (tiff_seek(tf, (long)ifd_address, SEEK_SET) != 0)
        return 4;

    /* now read IFD table */
    if (fgetuint16(tf, tf->little_endian, &(tf->ifd_len)))
        return 5;
    if (tf->ifd_len <= 0)
        return 5;
    tf->ifd_table = (TIFFDirEntry *) malloc(sizeof(TIFFDirEntry)
                                            * tf->ifd_len);
    buffer = (uint8 *) malloc(sizeof(TIFFDirEntry) * tf->ifd_len);
    tiff_read(buffer, sizeof(uint8), sizeof(TIFFDirEntry) * tf->ifd_len,
              tf);
    current = buffer;
    for (i = 0; i < (int)tf->ifd_len; i++) {
        tf->ifd_table[i].tdir_tag = getuint16(current, tf->little_endian);
        tf->ifd_table[i].tdir_type =
            getuint16(current + 2, tf->little_endian);
        tf->ifd_table[i].tdir_count =
            getuint32(current + 4, tf->little_endian);
        /* special workaround for short fields */
        if ((tf->ifd_table[i].tdir_type == TIFF_SHORT)
            && (tf->ifd_table[i].tdir_count <= 2))
            tf->ifd_table[i].tdir_offset = (uint32) getuint16(current + 8,
                                                              tf->
                                                              little_endian);
        else
            tf->ifd_table[i].tdir_offset =
                getuint32(current + 8, tf->little_endian);
#if 0
        printf("Tag: %5u: %5u %5u %5u\n",
               tf->ifd_table[i].tdir_tag, tf->ifd_table[i].tdir_type,
               (unsigned int)(tf->ifd_table[i].tdir_count),
               (unsigned int)(tf->ifd_table[i].tdir_offset));
#endif
        current += sizeof(TIFFDirEntry);
    }
    free(buffer);

    return 0;
}

/*
 * read and return the TIFF IFD entry corresponding to the given tag
 */
static int fget_tiff_field_entry(TIFF_FILE *tf, unsigned short tag,
                                 TIFFDirEntry * entry)
{
    /* assert that IFD table exists */
    if (tf && (tf->ifd_len > 0) && tf->ifd_table) {

        /* loop through IFD table */
        int i = 0;
        TIFFDirEntry *current = tf->ifd_table;
        while ((i < (int)tf->ifd_len - 1)
               && (current[i].tdir_tag < tag))
            i++;
        if (current[i].tdir_tag == tag) {
            memcpy(entry, &current[i], sizeof(TIFFDirEntry));
            return 1;
        }
    }
    return 0;
}

/* 
 * read the string corresponding to the given TIFF tag into the newly
 * created buffer description
 */
int fget_tiff_field_string(TIFF_FILE *tf, unsigned short tag, char **description)
{
    TIFFDirEntry entry;

    *description = NULL;

    if (fget_tiff_field_entry(tf, tag, &entry)
        && (entry.tdir_type == TIFF_ASCII) && (entry.tdir_count > 0)) {
        *description = (char *)malloc((entry.tdir_count) * sizeof(char));
        if (entry.tdir_count > 4) {
            if (tiff_seek(tf, (long)(entry.tdir_offset), SEEK_SET) != 0)
                return 0;
            if (tiff_read(*description, sizeof(char), entry.tdir_count, tf) !=
                (size_t)(sizeof(char) * entry.tdir_count))
                return 0;
        }
        else {
            memcpy(*description, &entry.tdir_offset, entry.tdir_count);
        }
        (*description)[entry.tdir_count - 1] = '\0';

        return entry.tdir_count;
    }

    return 0;
}

/*
 * Read the 16-bit unsigned short value corresponding to the given TIFF tag
 * If the tag has a 32-bit format, it is automatically converted to 16 bit.
 * If the field has more than 4 bytes, the IFD only holds an offset that
 * indicates the file position at which the values are stored.
 * Only the first value is returned, the others are ignored.
 * To read multiple entries, use fget_tiff_field_entry().
 * Returns 0 on success, non-zero values on error.
 */
int fget_tiff_field_uint16(TIFF_FILE *tf, unsigned short tag,
                           unsigned short *result)
{
    TIFFDirEntry entry;

    if (fget_tiff_field_entry(tf, tag, &entry)
        && ((entry.tdir_type == TIFF_SHORT)
            || (entry.tdir_type == TIFF_LONG))) {

        if (((entry.tdir_type == TIFF_SHORT) && (entry.tdir_count > 2))
            || ((entry.tdir_type == TIFF_LONG) && (entry.tdir_count > 1))) {
            /* value has more than 4 bytes; follow file offset in the IFD entry */
            int little_endian = get_tiff_little_endianness(tf);
            if (tiff_seek(tf, (long)(entry.tdir_offset), SEEK_SET) != 0)
                return 2;
            if (entry.tdir_type == TIFF_SHORT)
                return fgetuint16(tf, little_endian, result);
            else {
                uint32 tmp;
                int error = fgetuint32(tf, little_endian, &tmp);
                *result = (unsigned short)tmp;
                return error;
            }
        }
        else
            *result = (unsigned short)(entry.tdir_offset);
        return 0;
    }

    return 1;
}

/*
 * read the 32-bit unsigned long value corresponding to the given TIFF tag.
 * If the tag has a short format, it is automatically converted to 32 bit.
 * If the field has more than 4 bytes, the IFD only holds an offset that
 * indicates the file position at which the values are stored.
 * Only the first value is returned, the others are ignored.
 * To read multiple entries, use fget_tiff_field_entry().
 * Returns 0 on success, non-zero values on error.
 */
int fget_tiff_field_uint32(TIFF_FILE *tf, unsigned short tag,
                           unsigned int *result)
{
    TIFFDirEntry entry;

    if (fget_tiff_field_entry(tf, tag, &entry)
        && ((entry.tdir_type == TIFF_LONG)
            || (entry.tdir_type == TIFF_SHORT))) {

        if (((entry.tdir_type == TIFF_SHORT) && (entry.tdir_count > 2))
            || ((entry.tdir_type == TIFF_LONG) && (entry.tdir_count > 1))) {
            /* value has more than 4 bytes; follow file offset in the IFD entry */
            int little_endian = get_tiff_little_endianness(tf);
            int code = 0;
            if (tiff_seek(tf, (long)(entry.tdir_offset), SEEK_SET) != 0)
                return 2;
            if (entry.tdir_type == TIFF_SHORT) {
                uint16 tmp;
                code = fgetuint16(tf, little_endian, &tmp);
                *result = (unsigned int)tmp;
            }
            else {
                uint32 tmp;
                code = fgetuint32(tf, little_endian, &tmp);
                *result = (unsigned int)tmp;
            }
            return code;
        }
        else
            *result = entry.tdir_offset;
        return 0;
    }

    return 1;
}

/*
 * read 32-bit (4-byte) IEEE single precision
 * assumes little-endian byte order
 */
int fget_tiff_field_float(TIFF_FILE *tf, unsigned short tag, float *result)
{
    TIFFDirEntry entry;

    if (get_tiff_little_endianness(tf)
        && fget_tiff_field_entry(tf, tag, &entry)) {
        if (entry.tdir_type == TIFF_FLOAT) {
            tiff_seek(tf, (long)(entry.tdir_offset), SEEK_SET);
            tiff_read(result, sizeof(float), 1, tf);
            return 1;
        }
        if (entry.tdir_type == TIFF_DOUBLE) {
            double res;
            if (fget_tiff_field_double(tf, tag, &res)) {
                *result = res;
                return 1;
            }
        }
    }

    return 0;
}

/*
 * read 64-bit (8-byte) IEEE double precision tag
 * assumes little-endian byte order
 */
int fget_tiff_field_double(TIFF_FILE *tf, unsigned short tag, double *result)
{
    TIFFDirEntry entry;

    if (get_tiff_little_endianness(tf)
        && fget_tiff_field_entry(tf, tag, &entry)) {
        if (entry.tdir_type == TIFF_DOUBLE) {
            tiff_seek(tf, (long)(entry.tdir_offset), SEEK_SET);
            tiff_read(result, sizeof(double), 1, tf);
            return 1;
        }
    }

    return 0;
}

/*-----------------------------------------------------------------------*/
/* MetaMorph specific routines                                           */
/* NOTE: these always assume little-endian tiff files                    */
/*-----------------------------------------------------------------------*/
/*
 * create and read the table of 4-byte ints associated with the UIC1TAG.
 * this is a MetaMorph-specific TIFF extension. the table consists of
 * count pairs of LONG values; the first item of a pair is the tag ID,
 * the second a value or an offset.
 */
static int fget_tiff_uic1tags(TIFF_FILE * fp, unsigned int **table)
{
    TIFFDirEntry entry;
    int little_endian = 1;

    *table = NULL;
    if (fget_tiff_field_entry(fp, TIFFTAG_UIC1TAG, &entry)
        && (entry.tdir_count > 0)) {
        int i;

        *table = (unsigned int *)malloc(2 * entry.tdir_count *
                                        sizeof(unsigned int));
        tiff_seek(fp, (long)(entry.tdir_offset), SEEK_SET);
        for (i = 0; i < (int)(2 * entry.tdir_count); i++) {
            uint32 tmp;
            fgetuint32(fp, little_endian, &tmp);
            (*table)[i] = (unsigned int)tmp;
        }
        return entry.tdir_count;
    }

    return 0;
}

/*
 * returns the LONG value associated with the given MetaMorph tag ID
 */
int fget_tiff_uic1_long_field(TIFF_FILE * fp, unsigned int tag,
                              unsigned int *value)
{
    unsigned int *table;
    int ntags = fget_tiff_uic1tags(fp, &table);
    int success = 0;

    if (ntags) {
        int i;
        for (i = 0; i < 2 * ntags; i += 2)
            if (table[i] == tag) {
                *value = table[i + 1];
                success = 1;
                break;
            }
        free(table);
    }

    return success;
}

/*
 * returns an array of <nval> LONG values associated with 
 * the given MetaMorph tag ID
 */
int fget_tiff_uic1_longs_field(TIFF_FILE * fp, unsigned int tag,
                               unsigned int *values, int nval)
{
    unsigned int offset;
    int little_endian = 1;

    if (fget_tiff_uic1_long_field(fp, tag, &offset)) {
        int i;
        tiff_seek(fp, (long)offset, SEEK_SET);
        for (i = 0; i < nval; i++) {
            uint32 tmp;
            fgetuint32(fp, little_endian, &tmp);
            values[i] = tmp;
        }
        return 1;
    }

    return 0;
}

static double rational_to_double(uint32 numerator, uint32 denominator)
{
    return (double)(int)numerator / (float)(int)denominator;
}

/*
 * returns the RATIONAL value associated with the given MetaMorph tag ID
 * the value associated with the tag is the offset of two LONG numbers
 * containing the numerator and denominator of the desired floating 
 * point value.
 */
int fget_tiff_uic1_float_field(TIFF_FILE * fp, unsigned int tag, float *value)
{
    uint32 frac[2];

    if (fget_tiff_uic1_longs_field(fp, tag, (unsigned int *)frac, 2)) {
        *value = rational_to_double(frac[0], frac[1]);
        return 1;
    }
    return 0;
}

/*
 * read information for the MetaMorph stack file tag UIC4Tag.
 * the count for UIC4Tag contains the number of image planes, the value
 * contains an offset pointing to a list of pairs of a short 
 * containg a tag ID and the corresponding data; this is complicated by
 * the fact that the length of the data
 * is dependent on the tag. to make things worse, most of the tags used
 * seem not to be documented, and since the length of the data depends
 * on the tag it is impossible to search the whole list.
 * fortunately, the documented tags seem to come first, so we only read 
 * until we find an unkown tag. 
 */
int fget_tiff_uic4_longs_field(TIFF_FILE * tf, unsigned short tag,
                               unsigned int *values, int nval)
{
    TIFFDirEntry entry;
    int little_endian = 1;

    if (fget_tiff_field_entry(tf, TIFFTAG_UIC4TAG, &entry)) {
        unsigned short stag;
        int terminate = 0;

        tiff_seek(tf, (long)(entry.tdir_offset), SEEK_SET);
        do {
            fgetuint16(tf, little_endian, &stag);
            if (stag == tag) {  /* found the tag we are looking for */
                int i;
                for (i = 0; i < nval; i++) {
                    uint32 tmp;
                    fgetuint32(tf, little_endian, &tmp);
                    values[i] = tmp;
                }
                return 1;
            }
            else {              /* tag not relevant, try to find next item */
                switch (stag) {
                case UICTAG_STAGEPOSITION:
                case UICTAG_CAMERACHIPOFFSET:
                    tiff_seek(tf,
                              (long)(4 * entry.tdir_count * sizeof(unsigned int)),
                              SEEK_CUR);
                    break;
                default:
                    terminate = 1;
                    break;
                }
            }
        } while (!terminate);
    }

    return 0;
}

/*
 * struct to hold a MetaMorph user defined property.
 * the property consists of a length byte for the name, 
 * the name of the property, a type byte (1=float or 2=ASCII)
 * and a value. if the type is float, the value is given by a TIFF_RATIONAL,
 * else by a length byte plus the ASCII characters.
 */
struct tiff_uic_user_property {
    char *name;
    int type;
    float value;
    char *text;
};

/*
 * get a user-defined property from a stack file produced by
 * MetaMorph. 
 */
static int fget_tiff_offset_user_property(TIFF_FILE * tf, unsigned int offset,
                                          struct tiff_uic_user_property *prop)
{
    int len, i;
    int little_endian = 1;

    tiff_seek(tf, (long)offset, SEEK_SET);
    len = tiff_getc(tf);
    prop->name = (char *)malloc((len + 1) * sizeof(char));
    for (i = 0; i < len; i++)
        prop->name[i] = tiff_getc(tf);
    prop->name[len] = '\0';
    prop->type = tiff_getc(tf);
    prop->value = 0.;
    prop->text = NULL;
    if (prop->type == UICTAG_FLOAT) {
        unsigned int numerator, denominator;
        uint32 tmp;
        fgetuint32(tf, little_endian, &tmp);
        numerator = (unsigned int)tmp;
        fgetuint32(tf, little_endian, &tmp);
        denominator = (unsigned int)tmp;
        prop->value = (float)rational_to_double(numerator, denominator);
    }
    if (prop->type == UICTAG_ASCII) {
        len = tiff_getc(tf);
        prop->text = (char *)malloc((len + 1) * sizeof(char));
        for (i = 0; i < len; i++)
            prop->text[i] = tiff_getc(tf);
        prop->text[len] = '\0';
    }
    return 1;
}

/*
 * release memory allocated for a user defined property
 */
static void free_uic_user_property(struct tiff_uic_user_property *prop)
{
    free(prop->name);
    prop->name = NULL;
    if (prop->type == UICTAG_ASCII)
        free(prop->text);
    prop->type = 0;
}

/*
 * get a user-defined property from the table referenced by the 
 * UIC1TAG tiff tag.
 * user-defined properties are associated with tag ID 36.
 */
static int fget_tiff_uic1_user_property(TIFF_FILE * tf, char property[],
                                        struct tiff_uic_user_property *prop)
{
    unsigned int *table;
    int ntags = fget_tiff_uic1tags(tf, &table);
    if (ntags) {
        int i;
        for (i = 0; i < 2 * ntags; i += 2) {
            if (table[i] == UICTAG_IMAGEPROPERTY) {
                fget_tiff_offset_user_property(tf, table[i + 1], prop);
                if (!strcmp(property, prop->name))
                    return 1;
                free_uic_user_property(prop);
            }
        }
        free(table);
    }
    return 0;
}

/*
 * get a user defined property of type string from the table 
 * referenced by the UIC1TAG tiff tag
 */
int fget_tiff_image_string_property(TIFF_FILE *tf, char property[],
				    char **value)
{
    int success = 0;
    struct tiff_uic_user_property prop;
    if (fget_tiff_uic1_user_property(tf, property, &prop)) {
        if (prop.type == UICTAG_ASCII) {
            success = 1;
            *value = (char *)malloc((strlen(prop.text) + 1) * sizeof(char));
            strncpy(*value, prop.text, strlen(prop.text) + 1);
        }
        free_uic_user_property(&prop);
    }
    return success;
}

/*
 * get a user defined property of type float from the table 
 * referenced by the UIC1TAG tiff tag
 */
int fget_tiff_image_float_property(TIFF_FILE *tf, char property[],
				   float *value)
{
    int success = 0;
    struct tiff_uic_user_property prop;
    if (fget_tiff_uic1_user_property(tf, property, &prop)) {
        if (prop.type == UICTAG_FLOAT) {
            success = 1;
            *value = prop.value;
        }
        free_uic_user_property(&prop);
    }
    return success;
}

/*-----------------------------------------------------------------------*/
/* convenience wrapper functions                                         */
/*-----------------------------------------------------------------------*/
int fget_tiff_image_description(TIFF_FILE *tf, char description[], int len)
{
    char *result;
    int rlen = fget_tiff_field_string(tf, TIFFTAG_IMAGEDESCRIPTION, &result);
    if (rlen) {
        memcpy(description, result, MIN(len, rlen));
        description[len - 1] = '\0';
        free(result);
    }
    return MIN(len, rlen);
}

/*
 * this routine reads an image size from a tiff file.
 */
int fget_tiff_size(TIFF_FILE *tf, int *nx, int *ny, int *ns)
{
    TIFFDirEntry entry;
    unsigned int width, length;
    char msg[100];

    *nx = *ny = 0;
    *ns = 1;
    snprintf(msg, sizeof(msg), "direct");
    if (tf->cacheflag)
        snprintf(msg, sizeof(msg), "buffered");
    if (fget_tiff_field_uint32(tf, TIFFTAG_IMAGEWIDTH, &width)) {
        fprintf(stderr,
                "Error: %s::fget_tiff_size():%i Failed to read IMAGEWIDTH (%s).\n",
                __FILE__, __LINE__, msg);
        return 1;
    }
    *nx = width;
    if (fget_tiff_field_uint32(tf, TIFFTAG_IMAGELENGTH, &length)) {
         fprintf(stderr,
                "Error: %s::fget_tiff_size():%i Failed to read IMAGELENGTH (%s).\n",
                __FILE__, __LINE__, msg);
        return 1;
    }
    *ny = length;
    if (fget_tiff_field_entry(tf, TIFFTAG_UIC2TAG, &entry))
        *ns = entry.tdir_count;

    return 0;
}

/*
 * read a bytes*len bytes from file tf, starting at a given offset in the
 * data segment of the tiff file, and store them in buffer
 * Returns the number of bytes read;
 */
int fget_tiff_data(TIFF_FILE *tf, unsigned short *buffer, long len, long offset)
{
    int i;
    uint16 bps = 8;
    uint16 photometric = PHOTOMETRIC_MINISBLACK, compression = COMPRESSION_NONE;
    uint16 bytes;
    uint8 *temp;
    int little_endian = get_tiff_little_endianness(tf);
    unsigned int image_offset;

    if (fget_tiff_field_uint16(tf, TIFFTAG_BITSPERSAMPLE, &bps))
        return 1;
    if (fget_tiff_field_uint16(tf, TIFFTAG_COMPRESSION, &compression))
        return 2;
    fget_tiff_field_uint16(tf, TIFFTAG_PHOTOMETRIC, &photometric);
    bytes = bps / 8;
    if (photometric == PHOTOMETRIC_RGB) /* RGB images */
        bytes = 3;

    /* can not read compressed images */
    if (compression != COMPRESSION_NONE) {
        fprintf(stderr, "Error: %s::fget_tiff_data():%i "
                        "Compressed images not implemented.",
                        __FILE__, __LINE__);
        memset(buffer, 0, len * sizeof(unsigned short));
        return 0;
    }

    temp = (unsigned char *)malloc(sizeof(unsigned char) * len * bytes);

    /* Read the image data segment. This implementation only looks
     * at the offset of the first image strip to determine the location
     * of the data segment. More complicated layouts with multiple strips
     * are not supported at the moment. For MetaMorph stack files, 
     * a fixed offset needs to be given to forward to another image in
     * the stack.
     * TODO: Implement this properly and use all offsets! Need to use
     * fget_tiff_field_entry().
     */
    if (fget_tiff_field_uint32(tf, TIFFTAG_STRIPOFFSETS, &image_offset))
        return 4;
    if ((long)(image_offset + (offset + len) * bytes) > tf->data_length) {
        fprintf(stderr, "Error: %s::fget_tiff_data():%i "
                        "Data segment too short.", __FILE__, __LINE__);
        memset(buffer, 0, len * sizeof(unsigned short));
        return 7;
    }
    if (tiff_seek(tf, (long)(image_offset + offset * bytes), SEEK_SET))
        return 5;
    if (tiff_read(temp, sizeof(unsigned char), bytes * len, tf) !=
        sizeof(unsigned char) * bytes * len)
        return 6;
    /* convert samples to unsigned short */
    if (bytes == 2) {
        for (i = 0; i < len; i++)
            buffer[i] = getuint16(temp + (2 * i), little_endian);
    }
    else if (bytes == 3) { /* convert RGB to greyscale */
        for (i = 0; i < len; i++)
            buffer[i] = (unsigned short)(temp[bytes * i] | temp[bytes * i+1] 
                                         | temp[bytes * i+2]);
    }
    else {
         for (i = 0; i < len; i++)
            buffer[i] = (unsigned short)(temp[bytes * i]);
    }
    free(temp);
    return 0;
}

/*-----------------------------------------------------------------------*/
/* write out Tiff images using libtiff                                   */
/*-----------------------------------------------------------------------*/
#if defined(HAVE_LIBTIFF)

#include <tiffio.h>
#include <time.h>

/*
 * write out a (nx,ny) data array to a TIFF file
 * the data array is of size (nx*ny*bytesperpixel) bytes
 * description is an additional description written to the file
 */
int fwrite_tiff_image(char filename[], void *data,
                      int nx, int ny, int bytesperpixel, char description[])
{
    int nbytes;
    TIFF *tif = TIFFOpen(filename, "w");
    if (tif == NULL) {
        fprintf(stderr, "Error: Failed to open file %s\n", filename);
        return 0;
    }
    TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint16) nx);
    TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (uint16) ny);
    TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (uint16) (8 * bytesperpixel));
    TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
    TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
    TIFFSetField(tif, TIFFTAG_IMAGEDESCRIPTION, description);
    TIFFSetField(tif, TIFFTAG_XRESOLUTION, 1.);
    TIFFSetField(tif, TIFFTAG_YRESOLUTION, 1.);
    TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE);
    TIFFSetField(tif, TIFFTAG_SOFTWARE, "Solexa Prototype ");
    {
        time_t t = time(NULL);
        struct tm *ts = localtime(&t);
        char date[20];
        if (snprintf(date, sizeof(date), "%4d:%02d:%02d %02d:%02d:%02d",
                     ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday,
                     ts->tm_hour, ts->tm_min, ts->tm_sec) > 20)
            date[19] = '\0';
        TIFFSetField(tif, TIFFTAG_DATETIME, date);
    }

    nbytes = TIFFWriteRawStrip(tif, 0, data, nx * ny * bytesperpixel);
    if (nbytes <= 0)
        fprintf(stderr, "Could not write to file %s.\n", filename);

    TIFFClose(tif);
    return nbytes;
}

#endif
